﻿using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

// ReSharper disable once CheckNamespace
// 限定名称空间以减少引用

namespace System
{
    /// <summary>
    /// 针对枚举的扩展，会在运行时对类型进行检查
    /// </summary> 
    public static class EnumExtension
    {
        private static readonly ConcurrentDictionary<string, Dictionary<object, string>> EnumDescriptions
            = new ConcurrentDictionary<string, Dictionary<object, string>>();

        /// <summary>
        /// 返回一个以枚举值为Key，以Description的内容为Value的字典
        /// </summary>
        /// <param name="enumType"></param>
        /// <param name="nameAsEmptyDes">如果Description为空，是否使用Name代替</param>
        /// <returns></returns>
        public static Dictionary<object, string> AsValueDesDic(this Type enumType, bool nameAsEmptyDes = false)
        {
            Trace.Assert(enumType.IsEnum);
            Func<Type, Dictionary<object, string>> builder = type =>
            {
                var dic = new Dictionary<object, string>();
                foreach (var i in Enum.GetValues(type))
                {
                    var name = Enum.GetName(type, i);
                    var filed = type.GetField(name);
                    var attr = filed.GetCustomAttributes<DescriptionAttribute>()
                        .FirstOrDefault();
                    if (attr != null)
                        dic.Add(i, attr.Description);
                    else if (nameAsEmptyDes)
                        dic.Add(i, name);
                }

                return dic;
            };
            return EnumDescriptions.GetOrAdd(enumType.FullName, key => builder(enumType));
        }

        /// <summary>
        /// 返回一个以枚举值为Key，以Description的内容为Value的字典；其中Key接收一个转换
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumType"></param>
        /// <param name="valueSelector"></param>
        /// <param name="nameAsEmptyDes"></param>
        /// <returns></returns>
        public static Dictionary<T, string> AsValueDesDic<T>(this Type enumType,
            Func<object, T> valueSelector, bool nameAsEmptyDes = false)
        {
            Trace.Assert(enumType.IsEnum);

            var dic = new Dictionary<T, string>();
            foreach (var i in Enum.GetValues(enumType))
            {
                var name = Enum.GetName(enumType, i);
                var filed = enumType.GetField(name);
                var attr = filed.GetCustomAttributes<DescriptionAttribute>()
                    .FirstOrDefault();
                if (attr != null)
                    dic.Add(valueSelector(i), attr.Description);
                else if (nameAsEmptyDes)
                    dic.Add(valueSelector(i), name);
            }

            return dic;
        }

        /// <summary>
        /// 根据指定的Description值获取对应的枚举，如果有多个匹配，则产生一个异常
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumType"></param>
        /// <param name="description"></param>
        /// <returns></returns>
        public static T FetchEnumByDescription<T>(this Type enumType, string description)
        {
            Trace.Assert(enumType.IsEnum);
            var dic = enumType.AsValueDesDic(i => i);
            var value = dic.Single(i => i.Value == description);
            return (T) value.Key;
        }

        /// <summary>
        /// 获取指定的枚举的Name
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumValue"></param>
        /// <returns></returns>
        public static string FetchEnumName<T>(this T enumValue)
        {
            return Enum.GetName(typeof (T), enumValue);
        }

        /// <summary>
        /// 安全的Description转枚举操作
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumType"></param>
        /// <param name="description"></param>
        /// <returns></returns>
        public static object TryFetchEnumByDescription<T>(this Type enumType, string description)
        {
            Trace.Assert(enumType.IsEnum);
            try
            {
                return FetchEnumByDescription<T>(enumType, description);
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 读取指定的枚举值得Description信息，这是个稍微耗时的操作
        /// </summary>
        /// <param name="target"></param>
        /// <param name="handelError"></param>
        /// <returns></returns>
        public static string ReadEnumDescription(this object target, bool handelError = false)
        {
            Trace.Assert(target != null);
            var enumType = target.GetType();
            Trace.Assert(enumType.IsEnum);
            try
            {
                var dic = enumType.AsValueDesDic();
                return dic[target];
            }
            catch
            {
                if (!handelError)
                    throw;
                return null;
            }
        }

        /// <summary>
        /// 读取枚举值对应的多个Description信息，参考<see cref="FlagsAttribute"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static IEnumerable<string> ReadMultipleDescription<T>(this object value)
        {
            var enumType = typeof (T);
            Trace.Assert(enumType.IsEnum);
            Trace.Assert(enumType.GetCustomAttribute<FlagsAttribute>() != null);

            var intValue = (int) value;
            foreach (var eValue in Enum.GetValues(typeof (T)))
            {
                var target = (int) eValue;
                if ((intValue & target) == target)
                    yield return ((T) eValue).ReadEnumDescription();
            }
        }

        /// <summary>
        /// 获取一个枚举的数字字符串表现形式，T指定了枚举的基础类型
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string ToNumberString<T>(this object value)
        {
            var enumType = value.GetType();
            Trace.Assert(enumType.IsEnum);
            //Trace.Assert(enumType.GetCustomAttribute<FlagsAttribute>() != null);
            //Trace.Assert(Enum.GetUnderlyingType(enumType) == typeof (T));

            return ((int) value).ToString();
        }
    }
}
